package sicnu.cs.ich.es.service;

import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.GetIndexResponse;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.springframework.stereotype.Service;
import sicnu.cs.ich.common.exceptions.exception.BusinessException;
import sicnu.cs.ich.es.config.ElasticSearchIndex;

import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

/**
 * @author 沈洋 邮箱:1845973183@qq.com
 * @create 2021/11/29-20:59
 **/
@Slf4j
@Service
@RequiredArgsConstructor
public class ElasticService {

    private final RestHighLevelClient client;

    /**
     * 查询索引信息
     *
     * @param index 索引名称
     * @return 索引信息
     * @throws IOException 网络通信异常或者无法找到索引异常
     */
    public GetIndexResponse getIndexInfo(ElasticSearchIndex index) throws IOException {
        var request = new GetIndexRequest(index.getIndex());
        return client.indices().get(request, RequestOptions.DEFAULT);
    }


    /**
     * 新增文档
     *
     * @param index      索引
     * @param documentId 文档主键（最好使用文档本身主键）
     * @param document   文档
     * @return 是否新增成功
     */
    public boolean createDocument(ElasticSearchIndex index, String documentId, Object document) {
        var request = new IndexRequest(index.getIndex());
        // 设置规则
        request.id(documentId);
        // 超时时间
        request.timeout(TimeValue.timeValueSeconds(2));
        // 转json
        request.source(JSON.toJSONString(document), XContentType.JSON);

        IndexResponse response = null;
        try {
            response = client.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("新增文档失败{}", e.getMessage());
            throw new BusinessException("新增ES文档失败");
        }
        return response.status() == RestStatus.CREATED;
    }

    /**
     * 判断文档是否存在
     *
     * @param index 索引
     * @param id    文档id
     * @return 是否存在
     */
    public boolean isDocumentExists(ElasticSearchIndex index, final String id) {
        var request = new GetRequest(index.getIndex(), id);
        request.fetchSourceContext(new FetchSourceContext(false));
        try {
            return client.exists(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.info("查询ES异常{}", e.getMessage());
        }
        return false;
    }

    public <T> T getDocument(ElasticSearchIndex index, String id, Class<T> sourceClass) {
        return getDocument(index, id, sourceClass, null);
    }
    public <T> T getDocument(ElasticSearchIndex index, String id, Class<T> sourceClass, FetchSourceContext fetchSource) {
        var request = new GetRequest(index.getIndex(), id)
                .fetchSourceContext(fetchSource);
        GetResponse response = null;
        try {
            response = client.get(request, RequestOptions.DEFAULT);
            var map = response.getSource();
            return JSON.parseObject(response.getSourceAsString(), sourceClass);
        } catch (IOException e) {
            log.info("查询ES异常{}", e.getMessage());
            throw new BusinessException("获取ES文档异常");
        }
    }


    /**
     * 更新文档（局部更新）,传入的实体必须有getId方法
     * @param index 索引
     * @param document 文档实体
     * @param sourceClass 实体类
     * @return 是否更新成功
     * @throws ClassCastException 类型转换异常
     */
    public <T> boolean updateDocument(ElasticSearchIndex index , @NotNull Object document, Class<T> sourceClass) throws ClassCastException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        T source = null;
        if(!sourceClass.isInstance(document)){
            throw new ClassCastException("类型转换异常");
        }else {
            source = sourceClass.cast(document);
            var method = source.getClass().getMethod("getId");
            method.setAccessible(true);
            Object o = method.invoke(source);
            if (o==null){
                throw new BusinessException("该属性未赋值");
            }
            String id = String.valueOf(o);
            UpdateRequest request = new UpdateRequest(index.getIndex(),id);
            request.doc(JSON.toJSONString(document),XContentType.JSON);
            try {
                var response = client.update(request,RequestOptions.DEFAULT);
                return response.getResult() == DocWriteResponse.Result.UPDATED;
            } catch (IOException e) {
                log.error("文档更新失败{}",e.getMessage());
            }
            return false;
        }

    }

    /**
     * 删除文档
     * @param index 索引
     * @param id 文档主键
     * @return 是否删除
     */
    public boolean deleteDocument(ElasticSearchIndex index,String id) {
        var request = new DeleteRequest(index.getIndex(),id);
        try {
            var response = client.delete(request, RequestOptions.DEFAULT);
            return response.getResult()== DocWriteResponse.Result.DELETED;
        } catch (IOException e) {
            log.error("ES异常{}",e.getMessage());
        }
        return false;
    }



    /**
     * 根据搜索条件查询数据
     *
     * @param index         索引
     * @param sourceBuilder 查询条件
     */
    public SearchResponse searchDocument(ElasticSearchIndex index, @NotNull SearchSourceBuilder sourceBuilder) {
        var request = new SearchRequest(index.getIndex());
        request.source(sourceBuilder);
        SearchResponse response = null;
        try {
            response = client.search(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("ES查询失败");
            throw new BusinessException("查询失败");
        }
        return response;
    }


}
